<script lang="ts">
    import wcwidth from 'wcwidth';
    import Interactive, { Styles } from '$lib/Interactive';
    import { Main, VERSION_STORY } from '$lib/TestSceneMain';
	import { onMount } from 'svelte';
	import { VERSION_ENGINE } from '$lib/Definitions';

    export const VERSION_INTERACTIVE = '1.01';

    let header = '';
    let screenBuffer: Character[][] = [];
    let input: HTMLInputElement;
    let currentX = 0;

    let screen: HTMLElement;

    const bufWidth = 80;
    const bufHeight = 25;
    const wcPunctuation = '。，？！—…“”【】（）「」《》';

    class Character {
        char: string;
        invert = false;
        emphasis = false;
        underline = false;
        style = '';
        constructor(ch: string) {
            this.char = ch;
        }
    }

    function setStyle(c: Character, style: Styles) {
        switch (style) {
            case Styles.Default: break;
            case Styles.Meta:    c.style = 'meta'; break;
            case Styles.Quoted:  c.style = 'quoted'; break;
            case Styles.Input:   c.style = 'input'; break;
            case Styles.Comment: c.style = 'comment'; break;
        }
    }
    
    Interactive.printCentered = (text: string, format = true, style = Styles.Default) => {
        // currently, assumes each line is shorter than screen
        let line: Character[] = [];
        let len = 0, invert = false, emphasis = false, underline = false;

        function submitLine() {
            if (line.length > 0) {
                let l: Character[] = [];
                let pad = (bufWidth - len) / 2;
                for (let i = 0; i < pad; i++)
                    l.push(new Character(' '));
                line = l.concat(line);
            }
            screenBuffer.push(line);
            line = [];
            len = 0;
        }

        function doDefault(ch: string) {
            let c = new Character(ch);
            c.emphasis = emphasis;
            c.invert = invert;
            c.underline = underline;
            setStyle(c, style);
            line.push(c)
            len += wcwidth(ch);
        }

        for (let ch of text) {
            switch (ch) {
                case '_': 
                    if (format) underline = !underline; 
                    else doDefault(ch); 
                    break;
                case '*':
                    if (format) emphasis = !emphasis; 
                    else doDefault(ch); 
                    break;
                case '$':
                    if (format) invert = !invert; 
                    else doDefault(ch); 
                    break;
                case '\n': submitLine(); break;
                default: doDefault(ch); break;
            }
        }
        submitLine();
        Interactive.flush();
    }

    function print(text: string, format = true, style = Styles.Default) {
        if (text.length == 0) return;
        let word: Character[] = [];
        let curLine = screenBuffer[screenBuffer.length - 1];
        let curX = 0, invert = false, emphasis = false, underline = false;
        for (let ch of curLine)
            curX += wcwidth(ch.char);

        function newLine() {
            curLine = []; curX = 0;
            screenBuffer.push(curLine);
        }

        function submitChar(ch: Character) {
            let width = wcwidth(ch.char)
            if (curX + width > bufWidth) newLine();
            curLine.push(ch);
            curX += width;
        }

        function submitWord() {
            if (word.length == 0) return;

            let len = 0;
            for (let ch of word)
                len += wcwidth(ch.char);
            if (curX + len > bufWidth && curX > 0)
                newLine();
            for (let ch of word)
                submitChar(ch);
            word = []
        }

        function doDefault(ch: string, next: string | null) {
            let c = new Character(ch);
            c.emphasis = emphasis;
            c.invert = invert;
            c.underline = underline;
            setStyle(c, style);
            if (wcwidth(c.char) == 1 && c.char != ' ')
                word.push(c);
            else {
                // wide character
                if (wcPunctuation.includes(ch)) {
                    word.push(c);
                    return;
                }
                if (word.length > 0)
                    submitWord();
                if (next != null && wcPunctuation.includes(next))
                    word.push(c);
                else
                    submitChar(c);
            }
        }

        for (let i = 0; i < text.length; i++) {
            let ch = text[i], 
                next = i == text.length - 1 ? null : text[i+1];
            switch (ch) {
                case '_': 
                    if (format) underline = !underline; 
                    else doDefault(ch, next); 
                    break;
                case '*':
                    if (format) emphasis = !emphasis; 
                    else doDefault(ch, next); 
                    break;
                case '$':
                    if (format) invert = !invert; 
                    else doDefault(ch, next); 
                    break;
                case '\n':
                    submitWord();
                    newLine(); 
                    break;
                case ' ':
                    submitWord();
                    if (curX != bufWidth) doDefault(ch, next);
                    break;
                default: 
                    doDefault(ch, next); break;
            }
        }
        submitWord();
        Interactive.flush();
    }

    Interactive.print = print;

    Interactive.flush = () => {
        while (screenBuffer.length > bufHeight)
            screenBuffer.shift(); // remove first line

        if (header.length > 0) {
            // add header
            screenBuffer[0] = [];
            screenBuffer[1] = [];
            let lpad = Math.floor((bufWidth - wcwidth(header)) / 2);
            let rpad = bufWidth - wcwidth(header) - lpad;
            let space = new Character(' ');
            space.invert = true;
            for (let i = 0; i < lpad; i++)
                screenBuffer[0].push(space)
            for (let ch of header) {
                let c = new Character(ch);
                c.invert = true;
                screenBuffer[0].push(c);
            }
            for (let i = 0; i < rpad; i++)
                screenBuffer[0].push(space)
        }

        currentX = 0;
        for (let ch of screenBuffer[screenBuffer.length - 1])
            currentX += wcwidth(ch.char);
        screenBuffer = screenBuffer;
    }

    Interactive.clear = () => {
        for (let i = 0; i < screenBuffer.length; i++)
            screenBuffer[i] = [];
    }
    
    Interactive.setHeader = (text: string) => {
        header = text;
        Interactive.flush();
    }

    function focus() {
        input.focus();
    }

    Interactive.input = async function(text: string = '') {
        print(text);
        input.readOnly = false;
        
        await new Promise<void>((resolve, reject) => {
            let listener = (ev: KeyboardEvent) => {
                if (ev.key != 'Enter') return;
                input.removeEventListener('keydown', listener);
                resolve();
            };
            input.addEventListener('keydown', listener);
        });
        let result = input.value;
        input.value = '';
        input.readOnly = true;
        print(result + '\n', true, Styles.Input);
        return result;
    };

    Interactive.getKey = async function(text: string = '') {
        print(text);
        
        let result = await new Promise<string>((resolve, _) => {
            let listener = (ev: KeyboardEvent) => {
                input.removeEventListener('keydown', listener);
                resolve(ev.key);
            };
            input.addEventListener('keydown', listener);
        });
        input.value = '';
        return result;
    };

    onMount(() => {
        screenBuffer = [];
        for (let y = 0; y < bufHeight; y++)
        {
            let line: Character[] = [];
            for (let x = 0; x < bufWidth; x++)
                line.push(new Character(' '));
            screenBuffer.push(line)
        }
        input.readOnly = true;
        Main();
    });

    function processSelection(transfer: DataTransfer | null) {
        if (!transfer) return;
        const selection = window.getSelection();
        if (!selection?.rangeCount) return;
        const range = selection.getRangeAt(0);
        let text = '';
        range.cloneContents().querySelectorAll('td').forEach(e => {
            text += (!e.textContent || e.textContent == '') ? '' : e.textContent;
            if (e.nextSibling?.nodeType !== Node.ELEMENT_NODE) text += '\n';
        });
        text = text.trim();
        console.log(text);
        transfer.setData('text/plain', text);
    }
</script>

<style>
    :global(body) {
        background-color: black;
    }
    * {
        margin:0; padding:0;
        font-family: 'SF Mono', 'Menlo', monospace;
        font-size: 11pt;
        line-height: 1.3;
    }
    .container {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        
        padding: 5ch;
        width: fit-content;
        background-color: #224FBC;
    }
    .screen {
        display: block;
        color: rgb(232, 232, 232);
    }
    .inputline {
        position: relative;
        top: -1lh;
        display: flex;
    }
    .input { color: greenyellow; }
    .meta { color: lightblue; }
    .quoted { color: lightcoral; }
    .comment { color: rgb(232, 232, 232, 0.5); }
    .emphasis { font-weight: bold; filter: brightness(125%); }
    .underline { text-decoration: underline; }
    .invert {
        background-color: rgb(232, 232, 232);
        color: #224FBC;
    }
    table {
        border-collapse: collapse;
        width: 100%;
        table-layout: fixed;
    }
    tr {
        height: 1lh;
    }
    td {
        /* width: 1ch; */
        text-align: center;
        overflow: hidden;
    }
    input {
        display: inline-block;
        flex-grow: 1;
        color: white;
        background-color: transparent;
        border: none;
        outline: none;
        caret-shape: underscore;
    }
    .version {
        position: absolute;
        font-size: 8pt;
        color: gray;
        bottom: 5px;
        right: 5px;
        text-align: right;
    }
</style>

<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class=container on:click={focus}>
<div class=screen style="height:{bufHeight}lh; width:{bufWidth}ch" bind:this={screen}
on:copy={(e) => {processSelection(e.clipboardData); e.preventDefault()}}>
    <table>
        {#each screenBuffer as line} 
        <tr>
            {#each line as ch}
            <td colspan={wcwidth(ch.char)} class="{
                (ch.emphasis ? "emphasis " : "") +
                (ch.invert ? "invert " : "") +
                (ch.underline ? "underline " : "") + ch.style
            }">{ch.char}</td>
            {/each}
        </tr>
        {/each}
    </table>
    <div class=inputline>
        <span style="width:{currentX}ch"></span>
        <input type="text" bind:this={input} class="input" />
    </div>
</div>
</div>

<p class=version>
    engine: {VERSION_ENGINE}<br/>
    interactive-cli: {VERSION_INTERACTIVE}<br/>
    story: {VERSION_STORY}
</p>